﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.Threading;
using System.IO;

namespace CPGM
{
    public class CPGMReceiver
    {
        private const int CHUNK_MAX_SIZE = 1600;

        private Socket s;
        private AsyncCallback m_pfnCallBack;
        private IAsyncResult thisAsync;
        private List<CPGMPacket> packets;
        private UdpClient nakClient;
        private bool _isConnected = false;
        private bool _isListening = false;
        private bool _isOutOfOrder = false;
        private int expected_id = 0;
        private int thread_id = 0;
        private bool fool = true;

        public delegate void OnDataReceived(object sender, SocketPacketEventArgs spe);
        public event OnDataReceived DataReceived;

        public int NAKPortNumber { get; set; }

        public bool IsConnected
        {
            get { return _isConnected; }
            set { _isConnected = value; }
        }

        public bool IsListening
        {
            get { return _isListening; }
            set { _isListening = value; }
        }

        public bool Connect(string multicastGroup, string portNumber)
        {
            bool result = true;
            s = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);

            IPEndPoint ipep = new IPEndPoint(IPAddress.Any, int.Parse(portNumber));
            s.Bind(ipep);

            IPAddress ip = IPAddress.Parse(multicastGroup);

            s.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(ip, IPAddress.Any));
            _isConnected = true;
            _isListening = true;
            return result;
        }

        public void Disconnect()
        {
            _isListening = false;
            s.Close();
            s.Dispose();
            IsConnected = false;
        }

        public void ContinueReading()
        {
            byte[] b = new byte[CHUNK_MAX_SIZE];
            SocketPacket pack = new SocketPacket();
            pack.thisSocket = s;
            pack.dataBuffer = b;
            thisAsync = s.BeginReceive(b, 0, b.Length, SocketFlags.None, m_pfnCallBack, pack);
        }

        public void StartReceiving()
        {
            if (s != null)
            {
                fool = true;
                if (_isListening)
                {
                    packets = new List<CPGMPacket>();

                    _isListening = true;
                    if (m_pfnCallBack == null)
                    {
                        m_pfnCallBack = new AsyncCallback(AsyncDataReceived);
                    }
                    ContinueReading();
                }
                else
                {
                    s.Close();
                    IsConnected = false;
                }
            }
            else
            {
                throw new Exception("You have to connect first");
            }
        }

        public void SendNAK(object parameters)
        {
            bool _stopThread = false;
            NAKParameters NakParameters = (NAKParameters)parameters;
            thread_id = NakParameters.NakExpectedID;
            int count;
            // connect to client
            IPEndPoint ip = new IPEndPoint(IPAddress.Parse(NakParameters.NakPacket.LocalIPAddress), 662);
            UdpClient nClient = new UdpClient();
            while (!_stopThread)
            {
                //TODO: Send NAK with the expected_id missed in a thread
                Byte[] sendBytes = Encoding.ASCII.GetBytes(NakParameters.NakExpectedID.ToString());
                try
                {
                    count = nClient.Send(sendBytes, sendBytes.Length, ip);
                }
                catch (Exception e)
                {
                    Console.WriteLine(e.ToString());
                }

                Thread.Sleep(2000);
                if (expected_id > NakParameters.NakExpectedID)
                {
                    _stopThread = true;
                }
            }

        }

        public void RebuildFile()
        {
            MemoryStream ms = new MemoryStream();
            FileStream fs = new FileStream(@"d:\temp\newFile.txt", FileMode.Create, FileAccess.Write);
            fs.Close();
            foreach (CPGMPacket pack in packets)
            {
                ms.Write(pack.Data, 0, pack.Data.Length);
            }
            File.WriteAllBytes(@"d:\temp\newFile.txt", ms.ToArray());
        }

        public void AsyncDataReceived(IAsyncResult async)
        {
            SocketPacket sockPack = (SocketPacket)async.AsyncState;
            if (IsListening)
            {
                int rx = sockPack.thisSocket.EndReceive(async);

                CPGMPacket pack = new CPGMPacket();
                pack.LoadFromByteArray(sockPack.dataBuffer);


                // Initialize
                if (packets.Count == 0)
                {
                    expected_id = 0;
                }
                if (fool && expected_id == 2)
                {
                    fool = false;
                    ContinueReading();
                }
                else
                {
                    // Test if a packet is missed
                    if (expected_id != pack.ID)
                    {
                        // Need to sort the list to insert in the value at the good position
                        int temp = packets.FindIndex(delegate(CPGMPacket p)
                        {
                            return p.ID == pack.ID;
                        });
                        if (temp > -1)
                        {
                            //Alreqdy hqve
                        }
                        else
                        {
                            //Just adds and sort
                            packets.Add(pack);
                            packets.Sort(delegate(CPGMPacket p1, CPGMPacket p2)
                            {
                                return (p1.ID).CompareTo(p2.ID);
                            });
                            if (thread_id != expected_id)
                            {
                                System.Threading.Thread newThread;
                                newThread = new System.Threading.Thread(SendNAK);

                                // parameters for the thread
                                NAKParameters NakParameters = new NAKParameters();
                                NakParameters.NakExpectedID = expected_id;
                                NakParameters.NakPacket = pack;

                                // start thread
                                newThread.Start(NakParameters);
                            }
                        }
                    }
                    // Add packet in the list if it expected_id == pack.ID
                    else
                    {
                        packets.Add(pack);
                        packets.Sort(delegate(CPGMPacket p1, CPGMPacket p2)
                        {
                            return (p1.ID).CompareTo(p2.ID);
                        });
                        expected_id++;
                        for (int i = 0; i < packets.Count; i++)
                        {
                            if (packets[i].ID == expected_id && !packets[i].IsLast)
                                expected_id++;
                            else if (packets[i].ID > expected_id)
                            {
                                break;
                            }
                        }
                        if (expected_id == packets[packets.Count - 1].ID + 1 && packets[packets.Count - 1].IsLast)
                        {
                            RebuildFile();
                        }
                    }

                    SocketPacketEventArgs spe = new SocketPacketEventArgs(pack, rx);
                    DataReceived(this, spe);
                    ContinueReading();
                }
            }
        }
    }


    public class NAKParameters
    {
        public int NakExpectedID { get; set; }

        public CPGMPacket NakPacket { get; set; }
    }

}